using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace UnityEngine {

public class TextEditor {
#if UNITY_IPHONE || UNITY_ANDROID || UNITY_BB10 || UNITY_WP8 || UNITY_TIZEN
	public TouchScreenKeyboard keyboardOnScreen = null;
#endif

	public int pos = 0;
	public int selectPos = 0;
	public int controlID = 0;
	public GUIContent content = new GUIContent();
	public GUIStyle style = GUIStyle.none;
	public Rect position;
	public bool multiline = false;
	public bool hasHorizontalCursorPos = false;
	public bool isPasswordField = false;
	internal bool m_HasFocus;
	public Vector2 scrollOffset = Vector2.zero;	// The text field can have a scroll offset in order to display its contents

	// are we up/downing?
	public Vector2 graphicalCursorPos;
	public Vector2 graphicalSelectCursorPos;

	// Clear the cursor position for vertical movement...
	void ClearCursorPos () {hasHorizontalCursorPos = false; m_iAltCursorPos = -1;}
	
	// selection
	bool m_MouseDragSelectsWholeWords = false;
	int m_DblClickInitPos = 0;
	DblClickSnapping m_DblClickSnap = DblClickSnapping.WORDS;
	bool m_bJustSelected = false;
	
	int m_iAltCursorPos = -1; 
	
	public enum DblClickSnapping : byte { WORDS, PARAGRAPHS };
	
	public void OnFocus ()
	{
		if (multiline)
			pos = selectPos = 0;
		else 
			SelectAll ();
		m_HasFocus = true;
	}
	
	public void OnLostFocus ()
	{
		m_HasFocus = false;
		scrollOffset = Vector2.zero;
	}

	void GrabGraphicalCursorPos () {
		if (!hasHorizontalCursorPos) {
			graphicalCursorPos = style.GetCursorPixelPosition (position, content, pos);
			graphicalSelectCursorPos = style.GetCursorPixelPosition (position, content, selectPos); 
			hasHorizontalCursorPos = false;
		}
	}

	// Handle a key event.
	// Looks up the platform-dependent key-action table & performs the event
	// return true if the event was recognized.
	public bool HandleKeyEvent (Event e) {
		InitKeyActions (); 
		EventModifiers m = e.modifiers;
		e.modifiers &= ~EventModifiers.CapsLock;
		if (s_Keyactions.ContainsKey(e)) {
			TextEditOp op = (TextEditOp)s_Keyactions[e];
			PerformOperation (op);
			e.modifiers = m;
			return true;
		} 
		e.modifiers = m;		
		return false;
	}
	
	// Deletes previous text on the line
	public bool DeleteLineBack()
	{
		if (hasSelection)
		{
			DeleteSelection();
			return true;
		}
		int p = pos;
		int i = p;
		while (i-- != 0)
			if (content.text[i] == '\n') {
				p = i + 1;
				break;
			}
		if (i == -1)
			p = 0;		
		if (pos != p)
		{
			content.text = content.text.Remove (p, pos - p);
			selectPos = pos = p;
			return true;
		}
		return false;	
	}

	// Deletes the previous word
	public bool DeleteWordBack()
	{
		if (hasSelection)
		{
			DeleteSelection();
			return true;
		}

		int prevWordEnd = FindEndOfPreviousWord(pos);
		if(pos != prevWordEnd)
		{
			content.text = content.text.Remove(prevWordEnd, pos - prevWordEnd);
			selectPos = pos = prevWordEnd;
			return true;
		}
		return false;
	}

	// Deletes the following word
	public bool DeleteWordForward()
	{
		if (hasSelection)
		{
			DeleteSelection();
			return true;
		}

		int nextWordStart = FindStartOfNextWord (pos);
		if (pos < content.text.Length)
		{
			content.text = content.text.Remove(pos, nextWordStart - pos);
			return true;
		}
		return false;
	}

	// perform a right-delete
	public bool Delete () {
		if (hasSelection) {
			DeleteSelection ();
			return true;
		} else 
			if (pos < content.text.Length) {
				content.text = content.text.Remove (pos, 1);
				return true;
			}
		return false;
	}
	public bool CanPaste () {
		return GUIUtility.systemCopyBuffer.Length != 0;
	}
	// Perform a left-delete
	public bool Backspace () {
		if (hasSelection) {
			DeleteSelection ();
			return true;
		} else 
			if (pos > 0) {
				content.text = content.text.Remove (pos - 1, 1);
				selectPos = pos = pos - 1;
				ClearCursorPos ();
				return true;
			}
		return false;
	}
	
	/// Select all the text
	public void SelectAll () {
		pos = 0; selectPos = content.text.Length; 
		ClearCursorPos ();
	}
	
	/// Select none of the text
	public void SelectNone () {
		selectPos = pos; 
		ClearCursorPos ();
	}
	
	/// Does this text field has a selection
	public bool hasSelection { get { return pos != selectPos; } }

	/// Returns the selected text
	public string SelectedText { 
		get { 
			int len = content.text.Length;
			if (pos > len)
				pos = len;
			if (selectPos > len)
				selectPos = len;
			if (pos == selectPos)
				return "";
			if (pos < selectPos)
				return content.text.Substring(pos, selectPos - pos);
			else
				return content.text.Substring(selectPos, pos - selectPos);
		}
	} 

	/// Delete the current selection. If there is no selection, this function does not do anything...
	public bool DeleteSelection () {
		int len = content.text.Length;
		if (pos > len)
			pos = len;
		if (selectPos > len)
			selectPos = len;
		if (pos == selectPos)
			return false;
		if (pos < selectPos) {
			content.text = content.text.Substring (0, pos) + content.text.Substring (selectPos, content.text.Length - selectPos);
			selectPos = pos;
		} else {
			content.text = content.text.Substring (0, selectPos) + content.text.Substring (pos, content.text.Length - pos);
			pos = selectPos;
		}
		ClearCursorPos ();

		return true;
	}

	/// Replace the selection with /replace/. If there is no selection, /replace/ is inserted at the current cursor point.
	public void ReplaceSelection (string replace) {
		DeleteSelection ();
		content.text = content.text.Insert (pos, replace);
		selectPos = pos += replace.Length;
		ClearCursorPos ();
	}

	/// Replacted the selection with /c/
	public void Insert (char c) {
		ReplaceSelection (c.ToString());			
	}

	/// Move selection to alt cursor /position/
	public void MoveSelectionToAltCursor()
	{
		if (m_iAltCursorPos == -1)
			return;
		int p = m_iAltCursorPos;
		string tmp = SelectedText;		
		content.text = content.text.Insert (p, tmp);	
		
		if (p < pos)
		{
			pos += tmp.Length;
			selectPos += tmp.Length;
		}
		
		DeleteSelection();
		
		selectPos = pos = p;
		ClearCursorPos ();
			
	}

	/// Move the cursor one character to the right and deselect.
	public void MoveRight () {
		ClearCursorPos ();
		if (selectPos == pos) {
			pos++; 
			ClampPos ();
			selectPos = pos;
		} else {
			if (selectPos > pos)
				pos = selectPos;
			else
				selectPos = pos;	
		}
	}
	
	/// Move the cursor one character to the left and deselect.
	public void MoveLeft () {
		if (selectPos == pos) {
			pos--;
			if (pos < 0)
				pos = 0;
			selectPos = pos;
		} else {
			if (selectPos > pos) 
				selectPos = pos;
			else 
				pos = selectPos;	
		}
		ClearCursorPos ();
		return;
	}
	
	/// Move the cursor up and deselects.
	public void MoveUp () {
		if (selectPos < pos) 
			selectPos = pos;
		else 
			pos = selectPos;	
		GrabGraphicalCursorPos ();
		//graphicalCursorPos = style.GetCursorPixelPosition (position, content, pos);
		graphicalCursorPos.y -=  1;
		pos = selectPos = style.GetCursorStringIndex (position, content, graphicalCursorPos);
		if (pos <= 0) 
			ClearCursorPos ();		
	}
	
	/// Move the cursor down and deselects.
	public void MoveDown () {		
			if (selectPos > pos) 
				selectPos = pos;
			else 
				pos = selectPos;	
			GrabGraphicalCursorPos ();
			//graphicalCursorPos = style.GetCursorPixelPosition (position, content, pos);
			graphicalCursorPos.y += style.lineHeight + 5;
			pos = selectPos = style.GetCursorStringIndex (position, content, graphicalCursorPos);
			if (pos == content.text.Length) 
				ClearCursorPos ();
	
	}
	
	
	/// Moves the cursor to the start of the current line.
	public void MoveLineStart () {
		// we start from the left-most selected character
		int p = selectPos < pos ? selectPos : pos;
		// then we scan back to find the first newline
		int i = p;
		while (i-- != 0)
			if (content.text[i] == '\n') {
				selectPos = pos = i + 1;
				return;
			}
		selectPos = pos = 0;
	}
	
	/// Moves the selection to the end of the current line
	public void MoveLineEnd () {
		// we start from the right-most selected character
		int p = selectPos > pos ? selectPos : pos;
		// then we scan forward to find the first newline
		int i = p;
		int strlen = content.text.Length;
		while (i < strlen) {
			if (content.text[i] == '\n') {
				selectPos = pos = i;
				return;
			}
			i++;
		}
		selectPos = pos = strlen;
	}
	/// Move to the start of the current graphical line. This takes word-wrapping into consideration.
	public void MoveGraphicalLineStart () {
		pos = selectPos = GetGraphicalLineStart (pos < selectPos ? pos : selectPos);
	}
	/// Move to the end of the current graphical line. This takes word-wrapping into consideration.
	public void MoveGraphicalLineEnd () {
		pos = selectPos = GetGraphicalLineEnd (pos > selectPos ? pos : selectPos);
	}
	
	/// Moves the cursor to the beginning of the text
	public void MoveTextStart () {
		selectPos = pos = 0;
	}
	
	/// Moves the cursor to the end of the text
	public void MoveTextEnd () {
		selectPos = pos = content.text.Length;
	}
	
	/// Move to the next paragraph
	public void MoveParagraphForward () {
		pos = pos > selectPos ? pos : selectPos;
		if (pos < content.text.Length) {
			selectPos = pos = content.text.IndexOf ('\n', pos + 1);
			if (pos == -1)
				selectPos = pos = content.text.Length;
		}
	}
	/// Move to the previous paragraph
	public void MoveParagraphBackward () {
		pos = pos < selectPos ? pos : selectPos;
		if (pos > 1) {
			selectPos = pos = content.text.LastIndexOf ('\n', pos - 2) + 1;
		} else
			selectPos = pos =  0;	
	}
	
	// 
	
	// Move the cursor to a graphical position. Used for moving the cursor on MouseDown events.
	public void MoveCursorToPosition (Vector2 cursorPosition) {
		selectPos = style.GetCursorStringIndex(position, content, cursorPosition + scrollOffset);

		if (!Event.current.shift)
		{
			pos = selectPos;
		}

		ClampPos();
	}
	public void MoveAltCursorToPosition (Vector2 cursorPosition) {
		m_iAltCursorPos = style.GetCursorStringIndex (position, content, cursorPosition + scrollOffset);
		ClampPos();
	}
	
	public bool IsOverSelection(Vector2 cursorPosition) {
		int p = style.GetCursorStringIndex (position, content, cursorPosition + scrollOffset);
		return ((p < Mathf.Max(pos, selectPos)) && (p > Mathf.Min(pos, selectPos)));	
	}
	
	// Do a drag selection. Used to expand the selection in MouseDrag events.
	public void SelectToPosition (Vector2 cursorPosition) {
		if (!m_MouseDragSelectsWholeWords)
			pos = style.GetCursorStringIndex (position, content, cursorPosition + scrollOffset);
		else // snap to words/paragraphs
		{
			int p = style.GetCursorStringIndex (position, content, cursorPosition + scrollOffset);
			
			
			
			if (m_DblClickSnap == DblClickSnapping.WORDS)
			{ 			
				if (p < m_DblClickInitPos)
				{
					pos = FindEndOfClassification(p,-1);
					selectPos = FindEndOfClassification(m_DblClickInitPos,+1) ;
				}
				else
				{
					if (p >= content.text.Length)
						p = content.text.Length -1;
					pos = FindEndOfClassification(p,+1);
					selectPos = FindEndOfClassification(m_DblClickInitPos - 1,-1);	
				}
			} // paragraph
			else
			{
				if (p < m_DblClickInitPos)
				{
					if (p > 0)
						pos = content.text.LastIndexOf('\n', p - 2) + 1;
					else
						pos = 0;
					
					selectPos = content.text.LastIndexOf('\n', m_DblClickInitPos); 
				}		
				else
				{	
					if ( p < content.text.Length) 
					{
						pos = content.text.IndexOf('\n', p + 1) + 1;
						
						if (pos <= 0)
							pos = content.text.Length;	
					}
					else
						pos = content.text.Length;
					
					selectPos = content.text.LastIndexOf('\n', m_DblClickInitPos - 2) + 1;	
				}
				
			}				
		}
	}
	
	/// Expand the selection to the left
	public void SelectLeft () {
		if (m_bJustSelected)		
			if (pos > selectPos)
			{ // swap
				int tmp = pos;
				pos = selectPos;
				selectPos = tmp;	
			}	
		m_bJustSelected = false;
		
		pos--;
		if (pos < 0)
			pos = 0;	
	}
	
	public void SelectRight () {
		if (m_bJustSelected)		
			if (pos < selectPos)
			{ // swap
				int tmp = pos;
				pos = selectPos;
				selectPos = tmp;	
			}	
		m_bJustSelected = false;
		
		pos++;
		int stringlen = content.text.Length;
		if (pos > stringlen)
			pos = stringlen;
	}

	public void SelectUp () {
		GrabGraphicalCursorPos ();
		graphicalCursorPos.y -= 1;
		pos = style.GetCursorStringIndex (position, content, graphicalCursorPos);
	}
	
	public void SelectDown () {
		GrabGraphicalCursorPos ();
		graphicalCursorPos.y += style.lineHeight + 5;
		pos = style.GetCursorStringIndex (position, content, graphicalCursorPos);
	}

	/// Select to the end of the text
	public void SelectTextEnd () {
		// This is not quite like tha mac - there, when you select to end of text, the position of the cursor becomes somewhat i'll defined
		// Hard to explain. In textedit, try: CMD-SHIFT-down, SHIFT-LEFT for case 1. then do CMD-SHIFT-down, SHIFT-RIGHT, SHIFT-LEFT for case 2.
		// Anyways, it's fucked so we won't do that
		pos = content.text.Length;           
	}

	/// Select to the start of the text
	public void SelectTextStart () {
		// Same thing as SelectTextEnd...
		pos = 0;
	}
	
	/// sets whether the text selection is done by dbl click or not
	public void MouseDragSelectsWholeWords (bool on)
	{
		m_MouseDragSelectsWholeWords = on;	
		m_DblClickInitPos = pos;	
	}
	
	public void DblClickSnap(DblClickSnapping snapping)
	{
		m_DblClickSnap = snapping;
	}
	
	int GetGraphicalLineStart (int p) {
		Vector2 point = style.GetCursorPixelPosition (position, content, p);
		point.x =0;
		return style.GetCursorStringIndex (position, content, point);
	}

	int GetGraphicalLineEnd (int p) {
		Vector2 point = style.GetCursorPixelPosition (position, content, p);
		point.x += 5000;
		return style.GetCursorStringIndex (position, content, point);
	}

	int FindNextSeperator (int startPos) {
		int textLen = content.text.Length;
		while (startPos < textLen && !isLetterLikeChar (content.text[startPos]))
			startPos++;
		while (startPos < textLen && isLetterLikeChar (content.text[startPos]))
			startPos++;
		return startPos;
	}

	static bool isLetterLikeChar (char c) {
		return System.Char.IsLetterOrDigit (c) || c == '\'';
	}

	int FindPrevSeperator (int startPos) {
		startPos --;
		while (startPos > 0 && !isLetterLikeChar (content.text[startPos] ))
			startPos--;
							
		while (startPos >= 0 && isLetterLikeChar (content.text[startPos] ))
			startPos--;
		return startPos + 1;
	}

	/// Move to the end of the word.
	/// If the cursor is over some space characters, these are skipped
	/// Then, the cursor moves to the end of the following word.
	/// This corresponds to Alt-RightArrow on a Mac 
	public void MoveWordRight () {
		pos = pos > selectPos ? pos : selectPos;
		pos = selectPos = FindNextSeperator (pos);
		ClearCursorPos ();
	}

	public void MoveToStartOfNextWord () {
		ClearCursorPos ();
		if (pos != selectPos) {
			MoveRight ();
			return;
		}
		pos = selectPos = FindStartOfNextWord (pos);
	}

	public void MoveToEndOfPreviousWord () {
		ClearCursorPos ();
		if (pos != selectPos) {
			MoveLeft ();
			return;
		}
		pos = selectPos = FindEndOfPreviousWord (pos);
	}
	
	

	public void SelectToStartOfNextWord () {
		ClearCursorPos ();
		pos = FindStartOfNextWord (pos);
	}

	public void SelectToEndOfPreviousWord () {
		ClearCursorPos ();
		pos = FindEndOfPreviousWord (pos);
	}
	
	enum CharacterType {
		LetterLike,
		Symbol, Symbol2,
		WhiteSpace
	}
	
	CharacterType ClassifyChar (char c) {
		if (System.Char.IsWhiteSpace (c))
			return CharacterType.WhiteSpace;
		if (System.Char.IsLetterOrDigit (c) || c == '\'')
			return CharacterType.LetterLike;
		return CharacterType.Symbol;
	}

	/// Move to start of next word.
	/// This corresponds to Ctrl-RightArrow on Windows
	/// If the cursor is over a whitespace, it's moved forwards ''till the first non-whitespace character
	/// If the cursor is over an alphanumeric character, it''s moved forward 'till it encounters space or a punctuation mark. 
	/// If the stopping character is a space, this is skipped as well.
	/// If the cursor is over an punctuation mark, it's moved forward ''till it a letter or a space of a punctuation mark. If the stopping character is a space, this is skipped as well
	public int FindStartOfNextWord (int p) {
		int textLen = content.text.Length;
		if (p == textLen)
			return p;
		
		// Find out which char type we're at...
		char c = content.text[p];
		CharacterType t = ClassifyChar (c);
		if (t != CharacterType.WhiteSpace) {
			p++;
			while (p < textLen && ClassifyChar (content.text[p]) == t)
				p++;
		} else {
			if ( c == '\t' || c == '\n')
				return p + 1;
		}

		if (p == textLen)
			return p;
		
		// Skip spaces
		c = content.text[p];
		if (c == ' ') {							// If we're at a space, skip over any number of spaces
			while (p < textLen && System.Char.IsWhiteSpace(content.text[p])) 
				p++;
		} else if (c == '\t' || c == '\n') {			// If we're at a tab or a newline, just step one char ahead
			return p;
		}
		return p;
	}
	
	int FindEndOfPreviousWord (int p) {
		if (p == 0)
			return p;
		p--;

		// Skip spaces
		while (p > 0 && content.text[p] == ' ') 
			p--;
		
		CharacterType t = ClassifyChar (content.text[p]);
		if (t != CharacterType.WhiteSpace) {
			while (p >  0 && ClassifyChar (content.text[p - 1]) == t)
				p--;
		} 
		return p;
	}

	public void MoveWordLeft () {
		pos = pos < selectPos ? pos : selectPos;
		pos = FindPrevSeperator (pos);
		selectPos = pos;
	}

	public void SelectWordRight () {
		ClearCursorPos ();
		int cachedPos = selectPos;
		if (pos < selectPos) {
			selectPos = pos;
			MoveWordRight ();
			selectPos = cachedPos;
			pos = pos < selectPos ? pos : selectPos;
			return;
		} 
		selectPos = pos;
		MoveWordRight ();
		selectPos = cachedPos;
	}

	public void SelectWordLeft () {
		ClearCursorPos ();
		int cachedPos = selectPos;
		if (pos > selectPos) {
			selectPos = pos;
			MoveWordLeft ();
			selectPos = cachedPos;
			pos = pos > selectPos ? pos : selectPos;
			return;
		} 
		selectPos = pos;
		MoveWordLeft ();
		selectPos = cachedPos;
	}

	/// Expand the selection to the start of the line
	/// Used on a mac for CMD-SHIFT-LEFT
	public void ExpandSelectGraphicalLineStart () {
		ClearCursorPos ();
		if (pos < selectPos)
			pos = GetGraphicalLineStart (pos);
		else {
			int temp = pos;
			pos = GetGraphicalLineStart (selectPos);
			selectPos = temp;
		}
	}
	
	/// Expand the selection to the end of the line
	/// Used on a mac for CMD-SHIFT-RIGHT
	public void ExpandSelectGraphicalLineEnd () {
		ClearCursorPos ();
		if (pos > selectPos)
			pos = GetGraphicalLineEnd (pos);
		else {
			int temp = pos;
			pos = GetGraphicalLineEnd (selectPos);
			selectPos = temp;
		}
	}

	/// Move the selection point to the start of the line
	/// Used on a Windows for SHIFT-Home
	public void SelectGraphicalLineStart () {
		ClearCursorPos ();
		pos = GetGraphicalLineStart (pos);
	}
	
	/// Expand the selection to the end of the line
	/// Used on a mac for SHIFT-End
	public void SelectGraphicalLineEnd () {
		ClearCursorPos ();
		pos = GetGraphicalLineEnd (pos);
	
	}

	public void SelectParagraphForward () {
		ClearCursorPos ();
		bool wasBehind = pos < selectPos;
		if (pos < content.text.Length) {
			pos = content.text.IndexOf ('\n', pos + 1);
			if (pos == -1)
				pos = content.text.Length;
			if (wasBehind && pos > selectPos)
				pos = selectPos; 
		}
	}
	
	public void SelectParagraphBackward () {
		ClearCursorPos ();
		bool wasInFront = pos > selectPos;
		if (pos > 1) {
			pos = content.text.LastIndexOf ('\n', pos - 2) + 1;
			if (wasInFront && pos < selectPos)
				pos = selectPos;
		} else
			selectPos = pos =  0;	
	}

	/// Select the word under the cursor
	public void SelectCurrentWord () {
		ClearCursorPos ();

		int textLen = content.text.Length;
		selectPos = pos;

		// Handle that the text box can be empty
		if (textLen == 0)
			return;

		if (pos >= textLen)
			pos = textLen - 1;
		if (selectPos >= textLen) 
			selectPos--;

		if (pos < selectPos) {
			pos = FindEndOfClassification (pos, -1);
			selectPos = FindEndOfClassification (selectPos, +1);
		} else {
			pos = FindEndOfClassification (pos, +1);
			selectPos = FindEndOfClassification (selectPos, -1);
		}
		
		m_bJustSelected = true;
	}
	
	int FindEndOfClassification (int p, int dir) {
		int textLen = content.text.Length;
		
		if ( p >= textLen || p < 0 )
			return p;
		
		CharacterType t = ClassifyChar(content.text[p]);
		do {
			p += dir;	
			if (p < 0)
				return 0;
			if (p >= textLen)
				return textLen;
		} while (ClassifyChar(content.text[p]) == t);
		if (dir == 1)
			return p;
		return p + 1;
	}
	
	// Select the entire paragraph the cursor is on (separated by \n)
	public void SelectCurrentParagraph () {
		ClearCursorPos ();
		int textLen = content.text.Length;

		if (pos < textLen) {
			pos = content.text.IndexOf ('\n', pos);
			if (pos == -1)
				pos = content.text.Length;
			else
				pos++;
		}
		if (selectPos != 0) 
			selectPos = content.text.LastIndexOf ('\n', selectPos - 1) + 1;
	}
	
	// TODO: get the height from the font

	public void DrawCursor (string text) {
		string realText = content.text;
		int cursorPos = pos;
		if (Input.compositionString.Length > 0) 
		{
			content.text = text.Substring (0, pos) + Input.compositionString + text.Substring (selectPos);
			cursorPos += Input.compositionString.Length;
		}
		else
			content.text = text;

		graphicalCursorPos = style.GetCursorPixelPosition (new Rect (0,0,position.width,position.height), content, cursorPos);
		
		//Debug.Log("Cursor pos: " + graphicalCursorPos);
		
		Rect r = style.padding.Remove (position);

		Vector2 contentSize = new Vector2(style.CalcSize(content).x, style.CalcHeight(content, position.width));

		// If there is plenty of room, simply show entire string
		if (contentSize.x < position.width) 
		{
			scrollOffset.x = 0;
		} 
		else 
		{
			//go right
			if (graphicalCursorPos.x + 1 > scrollOffset.x + r.width)
				// do we want html or apple behavior? this is html behavior
				scrollOffset.x = graphicalCursorPos.x - r.width;
			//go left
			if (graphicalCursorPos.x < scrollOffset.x + style.padding.left) 
				scrollOffset.x = graphicalCursorPos.x - style.padding.left;
		}
		// ... and height/y as well
		// If there is plenty of room, simply show entire string
		// Debug.Log(contentSize.y + " < R : " + r);
		if (contentSize.y < r.height) 
		{
			scrollOffset.y = 0;
		} 
		else 
		{
			//go down
			if (graphicalCursorPos.y + style.lineHeight > scrollOffset.y + r.height + style.padding.top)
				scrollOffset.y = graphicalCursorPos.y - r.height - style.padding.top + style.lineHeight;
			//go up
			if (graphicalCursorPos.y < scrollOffset.y  + style.padding.top ) 
				scrollOffset.y = graphicalCursorPos.y - style.padding.top;
		}

		// This case takes many words to explain:
		// 1. Text field has more text than it can fit vertically, and the cursor is at the very bottom (text field is scrolled down)
		// 2. user e.g. deletes some lines of text at the bottom (backspace or select+delete)
		// 3. now suddenly we have space at the bottom of text field, that is now not filled with any content
		// 4. scroll text field up to fill in that space (this is what other text editors do)
		if (scrollOffset.y > 0 && contentSize.y - scrollOffset.y < r.height)
			scrollOffset.y = contentSize.y - r.height - style.padding.top - style.padding.bottom;

		scrollOffset.y = scrollOffset.y<0?0:scrollOffset.y;

		Vector2 originalContentOffset = style.contentOffset;
		style.contentOffset -= scrollOffset;
		style.Internal_clipOffset = scrollOffset;
		
		// Debug.Log ("ScrollOffset : " + scrollOffset);
		
		Input.compositionCursorPos = graphicalCursorPos + new Vector2(position.x, position.y + style.lineHeight) - scrollOffset;

		if(Input.compositionString.Length > 0)	
			style.DrawWithTextSelection (position, content, controlID, pos, pos + Input.compositionString.Length, true);
		else
			style.DrawWithTextSelection (position, content, controlID, pos, selectPos);
		
		if (m_iAltCursorPos != -1)
			style.DrawCursor(position, content, controlID, m_iAltCursorPos);
		
		// reset
		style.contentOffset = originalContentOffset;
		style.Internal_clipOffset = Vector2.zero;

		content.text = realText;
	}
	
	
	bool PerformOperation (TextEditOp operation) {
        
		switch (operation) {
// NOTE the TODOs below:
		case TextEditOp.MoveLeft: 			MoveLeft (); break;
		case TextEditOp.MoveRight:			MoveRight (); break;
		case TextEditOp.MoveUp:				MoveUp (); break;
		case TextEditOp.MoveDown:			MoveDown (); break;
		case TextEditOp.MoveLineStart:		MoveLineStart (); break;
		case TextEditOp.MoveLineEnd:		MoveLineEnd (); break;
		case TextEditOp.MoveWordRight:		MoveWordRight (); break;
		case TextEditOp.MoveToStartOfNextWord:		MoveToStartOfNextWord (); break;
		case TextEditOp.MoveToEndOfPreviousWord:		MoveToEndOfPreviousWord (); break;
		case TextEditOp.MoveWordLeft:		MoveWordLeft (); break;
		case TextEditOp.MoveTextStart:		MoveTextStart (); break;
		case TextEditOp.MoveTextEnd:		MoveTextEnd (); break;
		case TextEditOp.MoveParagraphForward:	MoveParagraphForward (); break;
		case TextEditOp.MoveParagraphBackward:	MoveParagraphBackward (); break;
//		case TextEditOp.MovePageUp:		return MovePageUp (); break;
//		case TextEditOp.MovePageDown:		return MovePageDown (); break;
		case TextEditOp.MoveGraphicalLineStart: MoveGraphicalLineStart (); break;
		case TextEditOp.MoveGraphicalLineEnd: MoveGraphicalLineEnd (); break;
		case TextEditOp.SelectLeft:			SelectLeft (); break;
		case TextEditOp.SelectRight:			SelectRight (); break;
		case TextEditOp.SelectUp:			SelectUp (); break;
		case TextEditOp.SelectDown:			SelectDown (); break;
		case TextEditOp.SelectWordRight:		SelectWordRight (); break;
		case TextEditOp.SelectWordLeft:		SelectWordLeft (); break;
		case TextEditOp.SelectToEndOfPreviousWord:	SelectToEndOfPreviousWord (); break;
		case TextEditOp.SelectToStartOfNextWord: 	SelectToStartOfNextWord (); break;

		case TextEditOp.SelectTextStart:		SelectTextStart (); break;
		case TextEditOp.SelectTextEnd:		SelectTextEnd (); break;
		case TextEditOp.ExpandSelectGraphicalLineStart: ExpandSelectGraphicalLineStart (); break;
		case TextEditOp.ExpandSelectGraphicalLineEnd: ExpandSelectGraphicalLineEnd (); break;
		case TextEditOp.SelectParagraphForward:		SelectParagraphForward (); break;
		case TextEditOp.SelectParagraphBackward:	SelectParagraphBackward (); break;
		case TextEditOp.SelectGraphicalLineStart: SelectGraphicalLineStart (); break;
		case TextEditOp.SelectGraphicalLineEnd: SelectGraphicalLineEnd (); break;
//		case TextEditOp.SelectPageUp:					return SelectPageUp (); break;
//		case TextEditOp.SelectPageDown:				return SelectPageDown (); break;
		case TextEditOp.Delete:								return Delete (); 
		case TextEditOp.Backspace:						return Backspace (); 
		case TextEditOp.Cut:									return Cut (); 
		case TextEditOp.Copy:								Copy (); break;
		case TextEditOp.Paste:								return Paste ();
		case TextEditOp.SelectAll:							SelectAll (); break;
		case TextEditOp.SelectNone:						SelectNone (); break;
//		case TextEditOp.ScrollStart:			return ScrollStart (); break;
//		case TextEditOp.ScrollEnd:			return ScrollEnd (); break;
//		case TextEditOp.ScrollPageUp:		return ScrollPageUp (); break;
//		case TextEditOp.ScrollPageDown:		return ScrollPageDown (); break;
		case TextEditOp.DeleteWordBack: return DeleteWordBack(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
		case TextEditOp.DeleteLineBack: return DeleteLineBack();
		case TextEditOp.DeleteWordForward: return DeleteWordForward(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
		default:
			Debug.Log ("Unimplemented: " + operation);
			break;
		}
		return false;
	}

	enum TextEditOp {
		MoveLeft, MoveRight, MoveUp, MoveDown, MoveLineStart, MoveLineEnd, MoveTextStart, MoveTextEnd, MovePageUp, MovePageDown, 
		MoveGraphicalLineStart, MoveGraphicalLineEnd, MoveWordLeft, MoveWordRight, 
		MoveParagraphForward, MoveParagraphBackward,  MoveToStartOfNextWord, MoveToEndOfPreviousWord,
		SelectLeft, SelectRight, SelectUp, SelectDown, SelectTextStart, SelectTextEnd, SelectPageUp, SelectPageDown,
		ExpandSelectGraphicalLineStart, ExpandSelectGraphicalLineEnd, SelectGraphicalLineStart, SelectGraphicalLineEnd, 
		SelectWordLeft, SelectWordRight, SelectToEndOfPreviousWord, SelectToStartOfNextWord,
		SelectParagraphBackward, SelectParagraphForward,
		Delete, Backspace, DeleteWordBack, DeleteWordForward, DeleteLineBack,
		Cut, Copy, Paste, SelectAll, SelectNone,
		ScrollStart, ScrollEnd, ScrollPageUp, ScrollPageDown
	};

	string oldText;
	int oldPos, oldSelectPos;

	public void SaveBackup () {
		oldText = content.text;
		oldPos = pos;
		oldSelectPos = selectPos;
	}

	public void Undo () {
		content.text = oldText;
		pos = oldPos;
		selectPos = oldSelectPos;
	}

	public bool Cut () {
		//Debug.Log ("Cut");
        if (isPasswordField)
            return false;
		Copy ();
		return DeleteSelection ();
	}
	
	public void Copy () {
		//Debug.Log ("Copy");
		if (selectPos == pos)
			return;

        if (isPasswordField)
            return;

		string copyStr;
		if (pos < selectPos)
			copyStr = content.text.Substring (pos, selectPos - pos);
		else 
			copyStr = content.text.Substring (selectPos, pos - selectPos);

		GUIUtility.systemCopyBuffer = copyStr;
	}
	
	public bool Paste () {
		//Debug.Log ("Paste");
		string pasteval = GUIUtility.systemCopyBuffer;
		if (pasteval != "") {
			ReplaceSelection (pasteval);
			return true;
		}
		return false;
	}

	static void MapKey (string key, TextEditOp action) {
		s_Keyactions [Event.KeyboardEvent (key)] = action;
	}
	static Dictionary<Event, TextEditOp> s_Keyactions;
	/// Set up a platform independant keyboard->Edit action map. This varies depending on whether we are on mac or windows.
	void InitKeyActions () {
		if (s_Keyactions != null) 
			return;
		s_Keyactions = new Dictionary<Event, TextEditOp> ();
		
		// key mappings shared by the platforms
		MapKey ("left", TextEditOp.MoveLeft);
		MapKey ("right", TextEditOp.MoveRight);
		MapKey ("up", TextEditOp.MoveUp);
		MapKey ("down", TextEditOp.MoveDown);

		MapKey ("#left", TextEditOp.SelectLeft);
		MapKey ("#right", TextEditOp.SelectRight);
		MapKey ("#up", TextEditOp.SelectUp);
		MapKey ("#down", TextEditOp.SelectDown);

		MapKey ("delete", TextEditOp.Delete);
		MapKey ("backspace", TextEditOp.Backspace);
		MapKey ("#backspace", TextEditOp.Backspace);

		// OSX is the special case for input shortcuts
		if (Application.platform == RuntimePlatform.OSXPlayer ||
		    Application.platform == RuntimePlatform.OSXWebPlayer ||
		    Application.platform == RuntimePlatform.OSXDashboardPlayer ||
		    Application.platform == RuntimePlatform.OSXEditor) {
            // Keyboard mappings for mac
// TODO		MapKey ("home", TextEditOp.ScrollStart);
// TODO		MapKey ("end", TextEditOp.ScrollEnd);
// TODO		MapKey ("page up", TextEditOp.ScrollPageUp);
// TODO		MapKey ("page down", TextEditOp.ScrollPageDown);

			MapKey ("^left", TextEditOp.MoveGraphicalLineStart);
			MapKey ("^right", TextEditOp.MoveGraphicalLineEnd);
// TODO		MapKey ("^up", TextEditOp.ScrollPageUp);
// TODO		MapKey ("^down", TextEditOp.ScrollPageDown);		

			MapKey ("&left", TextEditOp.MoveWordLeft);
			MapKey ("&right", TextEditOp.MoveWordRight);
			MapKey ("&up", TextEditOp.MoveParagraphBackward);
			MapKey ("&down", TextEditOp.MoveParagraphForward);	
			
			MapKey ("%left", TextEditOp.MoveGraphicalLineStart);
			MapKey ("%right", TextEditOp.MoveGraphicalLineEnd);
			MapKey ("%up", TextEditOp.MoveTextStart);
			MapKey ("%down", TextEditOp.MoveTextEnd);	
			
			MapKey ("#home", TextEditOp.SelectTextStart);
			MapKey ("#end", TextEditOp.SelectTextEnd);
// TODO			MapKey ("#page up", TextEditOp.SelectPageUp);
// TODO			MapKey ("#page down", TextEditOp.SelectPageDown);
					 
			MapKey ("#^left", TextEditOp.ExpandSelectGraphicalLineStart);
			MapKey ("#^right", TextEditOp.ExpandSelectGraphicalLineEnd);
			MapKey ("#^up", TextEditOp.SelectParagraphBackward);
			MapKey ("#^down", TextEditOp.SelectParagraphForward);		
				 
			MapKey ("#&left", TextEditOp.SelectWordLeft);
			MapKey ("#&right", TextEditOp.SelectWordRight);
			MapKey ("#&up", TextEditOp.SelectParagraphBackward);
			MapKey ("#&down", TextEditOp.SelectParagraphForward);	
				 
			MapKey ("#%left", TextEditOp.ExpandSelectGraphicalLineStart);
			MapKey ("#%right", TextEditOp.ExpandSelectGraphicalLineEnd);
			MapKey ("#%up", TextEditOp.SelectTextStart);
			MapKey ("#%down", TextEditOp.SelectTextEnd);	
			
			MapKey ("%a", TextEditOp.SelectAll);
			MapKey ("%x", TextEditOp.Cut);
			MapKey ("%c", TextEditOp.Copy);
			MapKey ("%v", TextEditOp.Paste);

			// emacs-like keybindings
			MapKey ("^d", TextEditOp.Delete);
			MapKey ("^h", TextEditOp.Backspace);
			MapKey ("^b", TextEditOp.MoveLeft);
			MapKey ("^f", TextEditOp.MoveRight);
			MapKey ("^a", TextEditOp.MoveLineStart);
			MapKey ("^e", TextEditOp.MoveLineEnd);
			// Can't be bothered to do these
			// MapKey ("^o", TextEditOp.InsertNewlineRight);
			// MapKey ("^t", TextEditOp.TransposeCharacters);

			MapKey("&delete", TextEditOp.DeleteWordForward);
			MapKey("&backspace", TextEditOp.DeleteWordBack);
			MapKey ("%backspace", TextEditOp.DeleteLineBack);
        } else {
			// Windows/Linux keymappings
            MapKey("home", TextEditOp.MoveGraphicalLineStart);
			MapKey ("end", TextEditOp.MoveGraphicalLineEnd);
// TODO		MapKey ("page up", TextEditOp.MovePageUp);
// TODO		MapKey ("page down", TextEditOp.MovePageDown);

			MapKey ("%left", TextEditOp.MoveWordLeft);
			MapKey ("%right", TextEditOp.MoveWordRight);
			MapKey ("%up", TextEditOp.MoveParagraphBackward);
			MapKey ("%down", TextEditOp.MoveParagraphForward);			

			MapKey ("^left", TextEditOp.MoveToEndOfPreviousWord);
			MapKey ("^right", TextEditOp.MoveToStartOfNextWord);
			MapKey ("^up", TextEditOp.MoveParagraphBackward);
			MapKey ("^down", TextEditOp.MoveParagraphForward);	
			
			MapKey ("#^left", TextEditOp.SelectToEndOfPreviousWord);
			MapKey ("#^right", TextEditOp.SelectToStartOfNextWord);
			MapKey ("#^up", TextEditOp.SelectParagraphBackward);
			MapKey ("#^down", TextEditOp.SelectParagraphForward);	

			MapKey ("#home", TextEditOp.SelectGraphicalLineStart);
			MapKey ("#end", TextEditOp.SelectGraphicalLineEnd);
// TODO			MapKey ("#page up", TextEditOp.SelectPageUp);
// TODO			MapKey ("#page down", TextEditOp.SelectPageDown);			

			MapKey("^delete", TextEditOp.DeleteWordForward);
			MapKey("^backspace", TextEditOp.DeleteWordBack);
			MapKey ("%backspace", TextEditOp.DeleteLineBack);

			MapKey ("^a", TextEditOp.SelectAll);
			MapKey ("^x", TextEditOp.Cut);
			MapKey ("^c", TextEditOp.Copy);
			MapKey ("^v", TextEditOp.Paste);
            MapKey("#delete", TextEditOp.Cut);
            MapKey("^insert", TextEditOp.Copy);
            MapKey("#insert", TextEditOp.Paste);
		}
	}

	// clamp cursor & selection to the string length
	public void ClampPos () {
		if (m_HasFocus == true && controlID != GUIUtility.keyboardControl)
			OnLostFocus ();
		if (m_HasFocus == false && controlID == GUIUtility.keyboardControl)
			OnFocus ();

		if (pos < 0) pos = 0;
		else if (pos > content.text.Length) pos = content.text.Length;
		if (selectPos < 0) selectPos = 0;
		else if (selectPos > content.text.Length) selectPos = content.text.Length;
		if (m_iAltCursorPos > content.text.Length) m_iAltCursorPos = content.text.Length;
		
	}
}	

} // namespace
